import os
import cPickle
import GUIconfig
import fcntl
import project_db
import shutil
from errors import ProjectError, ProjectFileNotFoundError 
import time

DBMS_TYPE_MONETDB = "monetdb"
DBMS_TYPE_SQLITE = "sqlite"

class project_settings:
    def __init__(self, name, root_dir):
        self.name = name
        self.root_directory = root_dir
        self.application_directory = os.path.join(self.root_directory, GUIconfig.project_app_dir_name)
        self.db_directory = os.path.join(self.root_directory, GUIconfig.project_db_dir_name)
        self.db_path = os.path.join(self.root_directory, GUIconfig.project_db_dir_name, GUIconfig.epistasis_database_file)
        self.project_file = os.path.join(self.root_directory, self.name+GUIconfig.project_file_extension) 
        self.epistasis_directory = os.path.join(self.application_directory, GUIconfig.epistasis_directory_name)
        self.epistasis_output_directory = os.path.join(self.root_directory, GUIconfig.epi_output_directory_name)
        self.epistasis_status_directory = os.path.join(self.epistasis_directory, GUIconfig.epi_status_directory_name)

        self.info = {"genotype_file":"", "phenotype_file":"", "class_file":""}
    
            
def set_project_info(attribute, value):
    prj = get_current_project()
    prj.info[attribute] = value
    
    try :
        f = open(prj.project_file, "w")
        cPickle.dump(prj,f)
        f.close()
    except OSError, e:
        print str(e)
        return 1

def get_project_info(attribute):
    prj = get_current_project()
    value = ""
    if prj.info.has_key(attribute):
        value = prj.info[attribute]
    return value

def using_monetdb():
    dbms_type = get_project_info("dbms")
    return (dbms_type == DBMS_TYPE_MONETDB)
    
def validate_project_file(file_path):
    #f = open(file_path, "r")
    #prj_dict = cPickle.load(f)
    #f.close()
    #if prj_dict.has_key()
    if not file_path.endswith(GUIconfig.project_file_extension):
        raise ProjectError("Invalid project file: %s" % file_path)
    print "valid project file"

def validate_project_dir(dir_path):
    if os.path.exists(dir_path):
        raise ProjectError("Project directory already exists: %s" % dir_path)
    print "valid project dir."

def update_prj_paths(prj_file_path):
    prj = get_current_project()
    
    if os.path.dirname(prj_file_path) != prj.root_directory:
        print "project location changed from %s to %s Updating paths." % (prj.root_directory, os.path.dirname(prj_file_path))
        updated_prj = project_settings(prj.name, os.path.dirname(prj_file_path))
        updated_prj.info = prj.info
        f = open(prj_file_path, "w")
        cPickle.dump(updated_prj, f)
        f.close()


def load_application_cache():
    if not os.path.exists(GUIconfig.application_cache_file):
        return {}
    f = open(GUIconfig.application_cache_file, "r")
    fcntl.flock(f.fileno(), fcntl.LOCK_SH)
    # TODO : use cross platform locking
    application_cache = cPickle.load(f)
    fcntl.flock(f.fileno(), fcntl.LOCK_UN)
    f.close()
    return application_cache 
    
def save_application_cache(application_cache):
    f = open(GUIconfig.application_cache_file, "w")
    fcntl.flock(f.fileno(), fcntl.LOCK_EX)
    # TODO : use cross platform locking
    cPickle.dump(application_cache, f)
    fcntl.flock(f.fileno(), fcntl.LOCK_UN)
    f.close()
    

def remove_from_recent_projects(path):
    cache = load_application_cache()
    
    if cache.has_key("recent_projects"):
        if path in cache["recent_projects"]:
            cache["recent_projects"].remove(path)
            
    print "removing from recent projects", path    
    save_application_cache(cache)


def add_to_recent_projects(path):
    MAX_RECENT_PROJECTS = 10 
    cache = load_application_cache()
    
    if cache.has_key("recent_projects"):
        if path in cache["recent_projects"]:
            cache["recent_projects"].remove(path) # move the path to the top
            cache["recent_projects"].append(path)
            
        else :
            if len(cache["recent_projects"]) >= MAX_RECENT_PROJECTS:
                cache["recent_projects"].pop(0)
            
            cache["recent_projects"].append(path)
        
    else:
        cache["recent_projects"] = [path]
    
    print "adding to recent projects", path    
    save_application_cache(cache)


def create_project(location, name):
    project_root = os.path.join(location, name)
    if os.path.exists(project_root):
        raise ProjectError("%s already exists" % project_root)
    
    print "creating dirs"
    #try:
        # create main directory
    os.mkdir(project_root)

        # create 2nd level directories
    for d in GUIconfig.project_directories:
        os.mkdir(os.path.join(project_root, d))
            
    #except OSError,e :
    #    print str(e)
    #    return 0

    
    project_settings_file = os.path.join(project_root, name+GUIconfig.project_file_extension)
    application_directory = os.path.join(project_root, GUIconfig.project_app_dir_name)
    output_directory =  os.path.join(project_root, GUIconfig.epi_output_directory_name)
    #db_directory = os.path.join(project_root, GUIconfig.project_db_dir_name)
    
    # create epistasis directories 
    epistasis_directory = os.path.join(application_directory, GUIconfig.epistasis_directory_name)
    
    os.mkdir(epistasis_directory)
    os.mkdir(os.path.join(epistasis_directory, GUIconfig.epi_status_directory_name))
    os.mkdir(output_directory)
    
    #except OSError,e :
    #    print str(e)
    #    return 0

    #db_path = os.path.join(project_root, GUIconfig.project_db_dir_name, GUIconfig.epistasis_database_file)
    prj_settings = project_settings(name, project_root)#, application_directory, db_directory, db_path)

    print "creating project file"    
    #try :
    f = open(project_settings_file, "w")
    cPickle.dump(prj_settings, f)
    f.close()
    #except OSError, e:
    #    print str(e)
    #    return 0
    
    # TODO : do some logging
    print "opening new project %s " % project_settings_file
    success = open_project(project_settings_file)
    
    return success  


def remove_current_project():
    """
    delete the entire project
    """
    prj_settings = get_current_project()
    close_current_project()
    remove_from_recent_projects(prj_settings.project_file)
    shutil.rmtree(prj_settings.root_directory)
    

def get_current_project():
    if not os.path.exists(GUIconfig.current_project_file): 
        if os.path.lexists(GUIconfig.current_project_file): # is there a broken symbolic link? 
            raise ProjectError("The current project %s links to an non-existing location." % GUIconfig.current_project_file)
        else:
            raise ProjectError("No current project file %s" % GUIconfig.current_project_file)
    
    f = open(GUIconfig.current_project_file, "r")
    project_settings = cPickle.load(f)
    f.close()
    return project_settings
    

def project_selected():
    """
    Returns info on whether a valid project is currently open. False if current_prj is a broken link.
    """
    
    if not os.path.exists(GUIconfig.current_project_file): 
        if os.path.lexists(GUIconfig.current_project_file): # is there a broken symbolic link? 
            #raise ProjectError("The current project %s links to an non-existing location." % GUIconfig.current_project_file)
            return False
        else:
            return False
    
    return True

def current_project_exists():
    """
    Returns whether a .current_prj link exists
    """
    
    # notify if broken link
    if not os.path.exists(GUIconfig.current_project_file) and os.path.lexists(GUIconfig.current_project_file):
        print "There is a broken symbolic link. %s " % GUIconfig.current_project_file
    
    return os.path.lexists(GUIconfig.current_project_file) # is true even when broken

def __set_current_project(project_file):   
    print "Setting current project", project_file
    if current_project_exists():
        close_current_project()
    os.symlink(project_file, GUIconfig.current_project_file)
    

def open_project(project_file):
    if not os.path.exists(project_file):
    #    remove_from_recent_projects(project_file)
        raise ProjectFileNotFoundError("No such project %s" % project_file )
    
    validate_project_file(project_file)
    
    add_to_recent_projects(project_file)    
    __set_current_project(project_file)
    update_prj_paths(project_file)
    return True
    
    
def close_current_project(stop_db=True):
    if not os.path.lexists(GUIconfig.current_project_file): # check for the symbolic link
        raise ProjectError("no open project to close")
        # TODO : throw more accurate exception
    if stop_db and using_monetdb():
        shutdown_db()
        time.sleep(2)

    print "Removing %s to close the current project." % GUIconfig.current_project_file
    os.remove(GUIconfig.current_project_file)
    

    
def import_genotypes(src_path, callback=None):
    current_prj = get_current_project()
    project_db.drop_table(current_prj.db_path, GUIconfig.genotype_table)
    
    success = project_db.import_genotypes(src_path, current_prj.db_path, callback)
    
    project_db.drop_table(current_prj.db_path, GUIconfig.snp_gene_table)
    
    return success and project_db.import_genotype_names(src_path, current_prj.db_path, callback)
    
        
def import_phenotypes(src_path, callback=None):
    current_prj = get_current_project()
    project_db.drop_table(current_prj.db_path, GUIconfig.phenotype_table)
     
    return project_db.import_phenotypes(src_path, current_prj.db_path, callback)


def import_classes(src_path, callback=None):
    current_prj = get_current_project()
    project_db.drop_table(current_prj.db_path, GUIconfig.class_table)
    
    return project_db.import_classes(src_path, current_prj.db_path, callback)

def import_class_by_partition(src_path, threshold, callback=None):
    current_prj = get_current_project()
    project_db.drop_table(current_prj.db_path, GUIconfig.class_table)
    
    return project_db.import_class_by_partition(src_path, threshold, get_current_project().epistasis_output_directory, current_prj.db_path, callback)


def import_genes(snp_gene_list, callback=None):
    """
    snp_gene_list - list of (snp, gene, chromosome nr.)
    """
    current_prj = get_current_project()
    #project_db.drop_table(current_prj.db_path, GUIconfig.class_table)    
    return project_db.import_genes(snp_gene_list, current_prj.db_path)


def extract_genotypes(names=None):
    current_prj = get_current_project()
    return project_db.extract_genotypes(current_prj.db_path, names)

def extract_genes(names=None):
    current_prj = get_current_project()
    return project_db.extract_genes(current_prj.db_path, names)

def extract_gene_snps(name):
    current_prj = get_current_project()
    return project_db.extract_gene_snps(current_prj.db_path, name)

def extract_subject_genotypes(names=None, ids=None):
    current_prj = get_current_project()
    return project_db.extract_genotypes(current_prj.db_path, names)

def extract_genotypes_for_single_class_type(names, class_type, classes):
    current_prj = get_current_project()
    return project_db.extract_epistasis_data_for_single_class_type(current_prj.db_path, GUIconfig.genotype_table, names, class_type, classes)

def extract_phenotypes_for_single_class_type(names, class_type, classes):
    current_prj = get_current_project()
    return project_db.extract_epistasis_data_for_single_class_type(current_prj.db_path, GUIconfig.phenotype_table, names, class_type, classes)

def extract_subject_data_for_single_class_type(names, class_type, classes):
    current_prj = get_current_project()
    
    # TODO: Change name
    return project_db.extract_epistasis_data_for_single_class_type2(current_prj.db_path, names, class_type, classes, above_zero=True)
 
def extract_phenotypes(names=None):
    current_prj = get_current_project()
    return project_db.extract_genotypes(current_prj.db_path, names)

def extract_classes(names=None):
    current_prj = get_current_project()
    return project_db.extract_classes(current_prj.db_path, names)

def extract_distinct_classes(names=None):
    current_prj = get_current_project()
    return project_db.extract_distinct_classes(current_prj.db_path, names)

def extract_genotype_info():
    current_prj = get_current_project()
    return project_db.extract_genotype_info(current_prj.db_path)

def extract_phenotype_names():
    current_prj = get_current_project()
    return project_db.extract_column_names(current_prj.db_path, GUIconfig.phenotype_table)

def extract_class_names():
    current_prj = get_current_project()
    return project_db.extract_column_names(current_prj.db_path, GUIconfig.class_table)

def table_size(name):
    current_prj = get_current_project()
    return project_db.table_size(current_prj.db_path, name)
    
def start_db(callback=None):
    current_prj = get_current_project()
    if using_monetdb():
        project_db.use_monetdb()
    else:
        project_db.use_sqlite()
        
    if callback:
        callback(50)
    return project_db.start_db(current_prj.db_path)
    #if callback:
    #    callback(100)


def kill_db(callback=None):
    """
    kill the DB daemon if possible
    """
    
    current_prj = None
    try:    
        current_prj = get_current_project()
    
    except ProjectError, e:
        print e
        return
    if callback:
        callback(5)
        
    print "Stopping the DB %s " % current_prj.db_path
    project_db.shutdown_db(current_prj.db_path, kill=True)
    time.sleep(5) # the daemon process takes 5-10 secs to shut down
    #if callback:
     #   callback(100)


def shutdown_db():
    """
    Stop the DB daemon if possible
    """
    
    current_prj = None
    try:    
        current_prj = get_current_project()
    
    except ProjectError, e:
        print e
        return
    #if callback:
    #    callback(5)
        
    print "Stopping the DB %s " % current_prj.db_path
    
    project_db.shutdown_db(current_prj.db_path)
 
#time.sleep(5) # the daemon process takes 5-10 secs to shut down
    #if callback:
     #   callback(100)